package uba.ing2.subite.services.impl;

import java.util.Date;

import uba.ing2.subite.domain.Account;
import uba.ing2.subite.domain.Debit;
import uba.ing2.subite.domain.DisposableCard;
import uba.ing2.subite.domain.Invoice;
import uba.ing2.subite.domain.PaidInvoice;
import uba.ing2.subite.domain.PaymentCard;
import uba.ing2.subite.domain.PaymentCardVisitor;
import uba.ing2.subite.domain.PrepaidCard;
import uba.ing2.subite.services.AccountService;
import uba.ing2.subite.services.Clock;
import uba.ing2.subite.services.PaymentAuthorizationResult;
import uba.ing2.subite.services.PaymentRegistrationService;
import uba.ing2.subite.services.PaymentRejectedException;
import uba.ing2.subite.services.PaymentService;
import uba.ing2.subite.services.authorizationrules.PaymentAuthorizationRule;

/**
 * Implementación de {@link PaymentService}.
 */
public class PaymentServiceImpl implements PaymentService {
	private Clock clock;
    private PaymentAuthorizationRule authorizationRules;
    private PaymentRegistrationService paymentRegistrationService;
	private AccountService accountService;

    public PaymentServiceImpl(Clock clock,
                                  PaymentAuthorizationRule authorizationRules,
                                  AccountService accountService, PaymentRegistrationService paymentRegistrationService) {
        this.clock = clock;
        this.authorizationRules = authorizationRules;
        this.accountService = accountService;
        this.paymentRegistrationService = paymentRegistrationService;
    }

    public PaidInvoice processPayment(Invoice invoice, PaymentCard card) throws PaymentRejectedException {
        PaymentAuthorizationResult result = authorizationRules.apply(invoice, card);

        if (result.isAuthorized()) {
            PaidInvoice paidInvoice = registerPayment(invoice, card);
            updateMoneyAvailable(paidInvoice, card);
			return paidInvoice;
        }

        throw new PaymentRejectedException(result.rejectionReason());
    }

	private void updateMoneyAvailable(PaidInvoice paidInvoice, PaymentCard card) {
		card.accept(new BalanceUpdater(paidInvoice));
	}

	private PaidInvoice registerPayment(Invoice invoice, PaymentCard card) {
		PaidInvoice paidInvoice = new PaidInvoice(clock.currentTime(), card, invoice);
		paymentRegistrationService.register(paidInvoice);
		return paidInvoice;
	}
	
    private class BalanceUpdater implements PaymentCardVisitor {
		private PaidInvoice paidInvoice;

		public BalanceUpdater(PaidInvoice paidInvoice) {
			this.paidInvoice = paidInvoice;
		}

		@Override
		public void visitDisposableCard(DisposableCard disposableCard) {
			disposableCard.updateMoneyAvailable(disposableCard.moneyAvailable().doubleValue() - paidInvoice.total().doubleValue());
		}

		@Override
		public void visitPrepaidCard(PrepaidCard prepaidCard) {
			Account account = accountService.accountIdentifiedBy(prepaidCard.accountIdentifier());
			Date date = clock.currentTime();
			account.register(new Debit(date, paidInvoice.total(), paidInvoice.shortDescription()));
			prepaidCard.updateMoneyAvailable(account.balanceOn(date));
		}

	}
}
